package tarantula.instrumenter;

import java.io.File;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import tarantula.util.FileUtils;
import tarantula.util.Globals;

public class Tracer {
	
	private static Map<SourcePos, Integer> exec_freq = new LinkedHashMap<SourcePos, Integer>();
	private static boolean instrument = false;
	
	public static final String trace_file_suffix = ".test";
	
	static final String SEP_FOR_FLUSH = "%%"; 
	static final String PASS_TEST = "PASSING";
	static final String FAIL_TEST = "FAILING";

	public static void turnOnInstrument() {
		instrument = true;
	}
	
	public static void turnOffInstrument() {
		instrument = false;
	}
	
	public static void cleanExecFreq() {
		exec_freq.clear();
	}
	
	public static void insertExecutedSourceCode(String packageName, String fileName, int lineNum, String jimpleCode) {
		if(!instrument) {
			return;
		}
		SourcePos pos = new SourcePos(packageName, fileName, lineNum, jimpleCode);
		if(!exec_freq.containsKey(pos)) {
			exec_freq.put(pos, 1);
		} else {
			exec_freq.put(pos, exec_freq.get(pos) + 1);
		}
	}
	
	public static void insertExecutedSourceCode(String pkgName, String fileName, String lnStr, String jimpleCode) {
		if(!lnStr.equals("null")){
			int ln = Integer.parseInt(lnStr);
			insertExecutedSourceCode(pkgName, fileName, ln, jimpleCode);
		}else{
			insertExecutedSourceCode(pkgName, fileName, -1, jimpleCode); // lineNum == -1 means we cannot get it from the Jimple method body.
		}
	}
	
	//start record execution trace at the beginning of each
	//JUnit test method.
	public static void startRecordExecution() {
		turnOnInstrument();
		cleanExecFreq();
	}
	
	/* The file format is:  sourcepos  frequency  pass_or_fail
	 * insert flush all recorded execution at *every* exit point
	 * of the JUnit test. Probably, a better way to do the instrumentation
	 * is to transform the JUnit source code as follows:
	 * 
	 * original test:
	 * 
	 * public void testMethodName() {
	 *     code_block
	 * }
	 * 
	 * after transformation:
	 * 
	 * public void testMethodName() *throws Throwable* {   //change the signature
	 *     Tracer.startRecordExecution();   //this must be executed
	 *     boolean isFailed = false;
	 *     try {
	 *         code_block;
	 *     } catch (Throwable e) {
	 *         isFailed = true;      //the test fails
	 *         throw e;
	 *     } finally {
	 *         Tracer.flushAndClean("./outputdir/" + testMethodName.test,  isFailed);
	 *     }
	 * }
	 * 
	 * An example can be found tests/tarantula.instrumenter.CodeTransformTest.java
	 * 
	 * I think we can omit instrument those common setUp(), and tearDown() methods,
	 * since they are not critical for localizing a bug.
	 * */
	public static void flushAndClean(String fileName, boolean isFailed) {
		fileName = fileName + trace_file_suffix;
		StringBuilder sb = new StringBuilder();
		String result = isFailed ? FAIL_TEST : PASS_TEST;
		for(SourcePos sourcePos : exec_freq.keySet()) {
			sb.append(sourcePos.toString());
			sb.append(SEP_FOR_FLUSH);
			sb.append(exec_freq.get(sourcePos));
			sb.append(SEP_FOR_FLUSH);
			sb.append(result);
			sb.append(Globals.lineSep);
		}
		//write to the file
		FileUtils.writeToFileNoExp(sb.toString(), fileName);
		//turn off the instrumentation
		turnOffInstrument();
	}
	
	public static boolean isPassingTestFile(File f) {
		String firstLine = null;
		try {
			firstLine = FileUtils.readFirstLine(f);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		if(firstLine == null) {
			throw new RuntimeException("File: " + f.getAbsolutePath() + " is empty.");
		}
		String[] execs = firstLine.trim().split(SEP_FOR_FLUSH);
		assert execs.length == 3 : "the length is: " + execs.length;
		
		String result = execs[2];
		assert result.equals(PASS_TEST) || result.endsWith(FAIL_TEST);
		
		return result.equals(PASS_TEST);
	}
	
	//check if the file is validity
	public static boolean checkFileValidity(File f) {
		String consistent_result = null;
		
		List<String> content = FileUtils.readWholeNoExp(f.getAbsolutePath());
		if(content.isEmpty()) {
			System.out.println("File: " + f.getAbsolutePath() + " is empty.");
			return false;
		}
		
		for(String singeline : content) {
			String[] execs = singeline.trim().split(SEP_FOR_FLUSH);
			assert execs.length == 3 : "The length is: " + execs.length;
			
			String result = execs[2];
			
			if(consistent_result == null) {
				consistent_result = result;
			} else {
				if(!consistent_result.equals(result)) {
					System.out.println("The result should be: " + consistent_result + ", "
							+ " but we see: " + result);
					return false;
				}
			}
		}
		
		return true;
	}
	
	//parse a file into a list of executed source code
	public static List<SourcePosExecInfo> parse(String fileName) {
		List<SourcePosExecInfo> execInfoList = new LinkedList<SourcePosExecInfo>();
		
		List<String> content = FileUtils.readWholeNoExp(fileName);
		for(String singleLine : content) {
			String line = singleLine.trim();
			if(line.equals("")) {
				continue;
			} else {
				String[] execs = line.split(SEP_FOR_FLUSH);
				assert execs.length == 3 : "The length is: " + execs.length;
				
				SourcePos pos = SourcePos.parse(execs[0]);
				Integer freq = Integer.parseInt(execs[1]);
				String result = execs[2];
				
				assert result.equals(PASS_TEST) || result.equals(FAIL_TEST);
				SourcePosExecInfo posExecInfo = new SourcePosExecInfo(pos, freq, result);
				
				execInfoList.add(posExecInfo);
			}
		}
		
		return execInfoList;
	}
}

